/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


/*
 * Hmm. 
 * To connect we need to know: 
 *  ip & port
 * To handshake we need to know:
 *  our peerid in this swarm, 
 *  combined capabilities of this peer (class) and this peer (system),
 *  (optionally) the peers id in this swarm,
 *  infoHash
 *  (optionally) piece state for have message
 * To send messages we need to know:
 *  interested/choked status
 * To send requests:
 *  interested/choked status,
 *  piece state?
 *  peer's piece state
 */
/**
 *
 * @implements RemotePeerFactory
 */
BasicRemotePeerFactory = function() {
    BitTorrentPeer 
    // peerId may be null or invalid
};

BasicRemotePeerFactory.prototype.dhgkdgkddkh = function() {
	var outputData = this._createLocalHandshake();
	//log("PeerEntity::connect " + this.remote.ip + ":" + this.remote.port + " writing handshake");
	//log("PeerEntity::connect " + this.remote.ip + ":" + this.remote.port + " handshake is: " + outputData);
	//log("PeerEntity::connect " + this.remote.ip + ":" + this.remote.port + " handshake is: " + binaryToHex(outputData));
	this._sendMsg(outputData);
	//outstream.write(outputData,outputData.length);

	/* Write BitField message */	
	outputData = this._createBitField();
	//outstream.write(outputData,outputData.length);
	this._sendMsg(outputData);
	// BITFIELD is: binaryToHex(outputData)
	streams.out.flush();
}


/**
 *
 * @param {ACString} host
 * @param {int} port
 * @param {ACString} peerId
 */
BasicRemotePeerFactory.prototype.createRemotePeer = function(host, port, peerId) {
    var peer;
    var transport;
    var streams;
    var streamListener;
    peer = new BasePeer(host, port, peerId);
    transport = this._createTransport(host, port, peerId);
    streams = this._createStreams(transport);
    peer.setTransport(transport);
    streamListener = new StreamDataListener(streams, peer);
    streams.pumpIn.asyncRead(streamListener,null);
    return peer;
}

BasicRemotePeerFactory.prototype._createTransport = function(host, port, peerId) {
    var ex;
    var transport;
    var transportService;
    try {
		// Socket transport service - used to open sockets 
		// https://developer.mozilla.org/En/NsISocketTransportService
		transportService =
		    Components.classes["@mozilla.org/network/socket-transport-service;1"]
		    .getService(Components.interfaces.nsISocketTransportService);
	    
		// Create transport to host/port.
		//  https://developer.mozilla.org/En/NsISocketTransportService#createTransport.28.29
		// nsISocketTransportService.createTransport returns nsISocketTransport
		//  https://developer.mozilla.org/en/nsISocketTransport
		// nsISocketTransport extends nsITransport
		//  https://developer.mozilla.org/en/nsITransport
		transport = transportService.createTransport(
		    null // string aSocketType : null is default 
		    ,0 // unsigned long aTypeCount : length of aSocketType
		    ,host // AUTF8String aHost : target hostname or IP
		    ,port // long aPort : target port
		    ,null // nsIProxyInfo aProxyInfo : null for no proxy
		);
		return transport;

	} catch (ex){
		reportError("Peer.connect exception`" + ex);
		throw ex;
	}    
}
/**
 *
 * @constructor
 */
StreamDataListener = function(streams, processor) {
    //this._peer = peer;
    this._streams = streams;
    this._processor = processor;
}

StreamDataListener.prototype = {
	data : "",
	dataTotal : 0,
	onStartRequest: function(request, context) {
		//peer._model.addEvent(new PeerConnected(peer));
		//peer._logComms("transport","recvd","connected");
	},
	onStopRequest: function(request, context, status) {
		//peer._disconnected();
		//peer._logComms("transport","recvd","disconnect status:" + status);
		//var e; // Exception
		//log("PeerEntity::datalistener::onStopRequest " + peer.remote.ip + ":" + peer.remote.port + " status:" + status);
	},
	onDataAvailable: function(request, context, inputStream, offset, count) {
		this.timeOfLastData = Date.now();
		// From instream, add to the string stream
		this.dataTotal += count;
		var newData =this._streams.wrappedIn.readBytes(count);       
		/*
        if (peer.stage == peer.STAGE_AWAITING_HANDSHAKE) {
			log("PeerEntity::datalistener::onDataAvailable " + peer.remote.ip + ":" + peer.remote.port + " newData:" + binaryToHex(newData));
		}
        */
		this.data = this.data + newData; 
		// Process data (may not have enough buffered)
		this._processor.processData(this);
	}

};

BasicRemotePeerFactory.prototype._createStreams = function(transport) {
    var streams = {};
    var ex;
	try {


		// NsISocketTransport.openOutputStream (inheritted from nsITransport)
		// returns a nsIOutputStream
		//  https://developer.mozilla.org/en/nsITransport#openOutputStream.28.29
		// 
		streams.out = transport.openOutputStream(
		     0 // unsigned long aFlags : 
		    ,0 // unsigned long aSegmentSize : 
		    ,0 // unsigned long aSegmentCount : 
		);
	
		// NsISocketTransport.openInputStream (inheritted from nsITransport)
		// returns a nsIInputStream
		//  https://developer.mozilla.org/en/nsITransport#openInputStream
		// because aFlags does NOT have OPEN_BLOCKING set, the returned
		// stream will also support nsIAsyncInputStream 
		// (see nsITransport.openInputStream for guarantee)
		//  https://developer.mozilla.org/en/NsIAsyncInputStream
		streams["in"] = transport.openInputStream(
		     2 // unsigned long aFlags : Trying Open_Unbuffered (2)
		    ,0 // unsigned long aSegmentSize : size of each buffer segment (0 is use default)
		    ,0 // unsigned long aSegmentCount : maximum number of buffer segments (0 is use default)
		);

		// Input stream. 
		//  https://developer.mozilla.org/en/NsIBinaryInputStream
		// Use nsIBinaryInputStream, nsIScriptableInputStream wasn't 
        // getting all the bytes from the buffer (nulls break it).
		//
		streams.wrappedIn = Components.classes["@mozilla.org/binaryinputstream;1"]
		    .createInstance(Components.interfaces.nsIBinaryInputStream);
		streams.wrappedIn.setInputStream(streams["in"]);

		var torrentInformation = {};
        var dataListener = new StreamDataListener(streams, peer);
        
		// nsIInputStreamPump
		// No documentation at MDC?? (Except use in a misc example)
		//  http://www.xulplanet.com/references/xpcomref/ifaces/nsIInputStreamPump.html
		// Takes care of the async calls on instream so we can consume using the above
		// dataListener object.
		streams.pumpIn = Components.
		    classes["@mozilla.org/network/input-stream-pump;1"].
		    createInstance(Components.interfaces.nsIInputStreamPump);
		streams.pumpIn.init(streams["in"], -1, -1, 0, 0, false);


	} catch (ex){
		reportError("Peer.connect exception");
		throw ex;
	}
	return streams;
}

BasicRemotePeerFactory.prototype._createLocalHandshake = function() {
	var msg;
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String/fromCharCode
	if ((msg = this._model.getValue("handshake")) == false) {
		msg = String.fromCharCode(19) + "BitTorrent protocol"; 
		msg += String.fromCharCode(0x00,0x00,0x00,0x00);
		msg += String.fromCharCode(0x00,0x00,0x00,0x00); // 8 reserved bytes
		msg += this._model.getValue("info_hash"); // info_hash
		msg += this._model.getValue("peer_id");
		if (msg.length != (49+19)) {
			log("PeerEntity::_createLocalHandshake msg length is: " + msg.length);
			log("PeerEntity::_createLocalHandshake msg length should be: " + (49+19));
		}
		this._model.setValue("handshake",msg)
	}
	return msg;

}

/**
 *
 * @implements BitTorrentPeer
 */
BasePeer = function(ip,port,peerIdentity) {
    this.ip = ip;
    this.port = port;
    this.peerIdentity = peerIdentity;

    this._listener = [];    

	var piecesLength = this._model.getPieceManager().getPieceCount();
	var index = 0;
	this.remote = {};
	/* See http://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29 */
	// Defaults are remote is chocking local & not interested in local,
	//  local is chocking remote & not interested in remote.
	this.remote.choking_local = true; /* if choked, do not send requests */
	this.remote.interested_in_local = false;
	this.remote.handshake_to_local = "";
	this.remote.ip = info["ip"];
	this.remote.port = info["port"];
	this.remote.peer_id = (("peer id" in info) ? info["peer id"] : false);
	this.remote.timeOfLastMsg = Date.now();
	this.remote.timeOfLastData = Date.now();
	this.remote.pieces = "";
	this.local = {};
	this.local.timeOfLastData = Date.now();
	this.local.choking_remote = true;
	this.local.interested_in_remote = false;
	this.local.handshake_to_remote = "";	
	this.local.sentPieceBytes = 0; // how many bytes of pieces we have sent the peer
	this.piece = {};
	this.piece.index = -1;
	this.piece.data = "";
	this.piece.requestedUpToIndex = -1;
    this.piece.pendingRequests = [];
	this.piece["piece length"] = -1;
	this.pieces = "";
	this._outstream = false;
	this._disconnectedTime = 0;
	this._comms = [];
	this.wants = []; // list of pieces that this peer has, that we want
	this.theyWants = []; // pieces the peer wants. oh boy that's a bad name. but at 3:45AM my brain doesn't think of names well. TODO: rename.
	// It's 4:18AM over a week (less than a month) later, and that name actually looks good. Pity I'm not using it.

	// Treat as 3 separate stages, because 
	// http://wiki.theory.org/BitTorrentSpecification#Handshake
	// says we _must_ respond on seeing info_hash,
	// then we may or may not get the peer_id
	this.STAGE_AWAITING_HANDSHAKE = 0;
	this.STAGE_AWAITING_PEERID = 1;
	this.STAGE_HANDSHAKED = 2;
	this.stage = this.STAGE_AWAITING_HANDSHAKE;	
	this.EXCEPTION_INFO_VIOLATION = "EXCEPTION_INFO_VIOLATION";
	this.EXCEPTION_DATA_SHORT = "EXCEPTION_DATA_SHORT";
	this.KEEPALIVE_TIMEOUT = 89 * 1000; // Client's expect maybe a 2 minute keepalive timeout.
				// So if it gets greater than 1:29, send keepalive.
	this.MAX_PEER_TIMEOUT = 4 * 60 * 1000; // We expect some communication back every 4 minutes.
				// If no communication, dump the peer
	this.RECONNECT_TIMEOUT = 3 * 60 * 1000; // Don't connect again in 4 mins
	log("PeerEntity::Constructor created peer for " + this.remote.ip + ":" + this.remote.port);

	this._neverReconnect = false;
}
BasePeer.prototype.ip = "";
BasePeer.prototype.port = 0;
BasePeer.prototype.isConnected = function() {
	return (this._streams.out != false);
}
BasePeer.prototype.setTransport = function(transport) {
    this._transport = transport;
}
BasePeer.prototype.stop = function() {
	if (this.isConnected()) {
		this._transport.close();
		this._disconnected();
		//this._model.getPeerManager().peerShutdown(this);
		
	}
}

/**
 * @param {BitTorrentPeerListener} listener
 */
BasePeer.prototype.addPeerListener = function(listener) {
    this._listener.push(listener);
}

/**
 * @param {BitTorrentPeerListener} listener
 */
BasePeer.prototype.removePeerListener = function(listener) {
    var index;
    while ((index = this._listener.indexOf(listener)) != -1) {
        this._listener.splice(index,1);
    }
}


/**
 *
 */
BasePeer.prototype._disconnected = function() {
	this._disconnectedTime = Date.now();
    this._dispatchDisconnect(null);
}
/**
 * 
 * @param obj
 */
BasePeer.prototype._dispatchInformation = function (obj) {
    var i;
    var listener;
    for (i=0; i < this._listener.length; i++) {
        listener = this._listener[i];
        listener.update(this, obj);
    }
}
/**
 * 
 * @param obj
 */
BasePeer.prototype._dispatchError = function (obj) {
    var i;
    var listener;
    for (i=0; i < this._listener.length; i++) {
        listener = this._listener[i];
        listener.onError(this, obj);
    }
}
/**
 * 
 * @param obj
 */
BasePeer.prototype._dispatchDisconnect = function (obj) {
    var i;
    var listener;
    for (i=0; i < this._listener.length; i++) {
        listener = this._listener[i];
        listener.onDisconnect(this, obj);
    }
}

/**
 * 
 * @param streamListener
 */
BasePeer.prototype.processData = function(streamListener) {

	var data = "";	
	var e; // Exception
	// Check what stage we're at
	data = streamListener.data;
	while (true) {
		try {			
			switch (this.stage) {
			case this.STAGE_AWAITING_HANDSHAKE:							
				data = this._attemptHandshake(data);
				this.stage = this.STAGE_AWAITING_PEERID;
				log("PeerEntity:processDataListener " + this.remote.ip + ":" + this.remote.port + " changed stage to STAGE_AWAITING_PEERID");			
				break;
			case this.STAGE_AWAITING_PEERID:
				data = this._attemptPeerId(data);
				this.stage = this.STAGE_HANDSHAKED;			
				log("PeerEntity:processDataListener " + this.remote.ip + ":" + this.remote.port + " changed stage to STAGE_HANDSHAKED");			
				break;
			case this.STAGE_HANDSHAKED:
				data = this._attemptMsg(data);
				log("PeerEntity:processDataListener " + this.remote.ip + ":" + this.remote.port + " Successfully processed a msg");			
				break;
			default:
								
			}
			streamListener.data = data;
		} catch (e if e == this.EXCEPTION_DATA_SHORT) {	
			// This type of conditional catch is discussed here:
			// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Exception_Handling_Statements/try...catch_Statement
			// Cool. Data is short, re-assign remaining data as the buffer.
			//log("PeerEntity:processDataListener " + this.remote.ip + ":" + this.remote.port + " Data Short");			
			// Re-assign is already done.
			return;
		} catch (e if e == this.EXCEPTION_INFO_VIOLATION) {	
			// Cool. They have violated some protocol thing, drop the connection.
			// eg. Peer id didn't match the expected peer id
            this._dispatchError({description:"Error on processing incoming stream: Violated protocol");
			this.stop();
			return;
		}
	}	
}
BasePeer.prototype._attemptMsg = function(data) {
	var msg = "";
	if (data.length < 4) {
		// Less than 4 bytes, not enough to read pstr len (first 4 bytes)
		throw this.EXCEPTION_DATA_SHORT;
	}
	// First 4 bytes are the message length
	var msglen = (data.charCodeAt(0) << 24)
		| (data.charCodeAt(1) << 16)
		| (data.charCodeAt(2) << 8)
		| (data.charCodeAt(3) << 0);
	/*
	// Check we have a msg id (required for everything but the keepalive)
	if (data.length < 5) {
		// Less than 5 bytes, not enough to read message id
		//log("Peer _attemptMsg : " + this.remote.ip + ":" + this.remote.port + " Data short! data is " + data.length);			
		throw this.EXCEPTION_DATA_SHORT;
	}
	*/
	if (data.length < (msglen + 4)) {
		// buffer isn't as big as msg length. 
		// Don't have full message yet.
		//log("Peer _attemptMsg : " + this.remote.ip + ":" + this.remote.port + " Data short! data is " + data.length + " msglen is " + msglen);			
		throw this.EXCEPTION_DATA_SHORT;			
	}
	// Check if msg is keep-alive
	if (msglen == 0) {
		// Keep-alive. Reset timeOfLastMsg.
		this.remote.timeOfLastMsg = Date.now();
		//this._logComms("data","recvd",data.substr(0,msglen + 4));
		// Return the unused string (We have consumed bytes 0,1,2,3)
		return data.substr(4);
	}
	//log("Peer _attemptMsg : " + this.remote.ip + ":" + this.remote.port + " Attempting message " + data.charCodeAt(4));			
    //data.charCodeAt(4);
	switch (data.charCodeAt(4)) {
	case 0:
		/* choke */
		this.remote.choking_local = true;
        this._dispatchInformation({
            type:"CHOKE"
        });
		return data.substr(5); /* Consumed 0-4 inclusive (5 bytes) */
		break;
	case 1:
		/* unchoke */
		this.remote.choking_local = false;
		this._dispatchInformation({
            type:"UNCHOKE"
        });
		return data.substr(5); /* Consumed 0-4 inclusive (5 bytes) */
		break;
	case 2:
		/* interested */
		this._dispatchInformation({
            type:"INTERESTED"
        });	
		this.remote.interested_in_local = true;
		return data.substr(5); /* Consumed 0-4 inclusive (5 bytes) */
		break;
	case 3:
		/* not interested */
		this._dispatchInformation({
            type:"NOT_INTERESTED"
        });	
		this.remote.interested_in_local = false;
		return data.substr(5); /* Consumed 0-4 inclusive (5 bytes) */
		break;
	case 4:
		/* have */

		// Need 4 more bytes
		if (data.length < (5 + 4)) {
			// Less than 9 bytes, not enough to read have msg
			throw this.EXCEPTION_DATA_SHORT;
		}
		// Decode piece number
		this._remoteHaveMsg(decode4CharInt(data.substr(5,4)));

		return data.substr(9); /* Consumed 0-8 inclusive (9 bytes) */
		break;
	case 5:
		/* bitfield */
		// Need as many bytes as msglen says, minus the msgid byte
		this._remoteBitfieldMsg(data.substr(5,msglen - 1));

		return data.substr(4 + msglen); /* Consumed 0-(msglen+4 -1) inclusive (msglen+4 bytes) */
		break;
	case 6:
		/* request */
		// Need 12 more bytes
		if (data.length < (5 + 12)) {
			// Less than 17 bytes, not enough to read request msg
			throw this.EXCEPTION_DATA_SHORT;
		}
		// Decode index, byte offset, and length
		this._remoteRequestMsg(
			decode4CharInt(data.substr(5,4))
			,decode4CharInt(data.substr(9,4))
			,decode4CharInt(data.substr(13,4))
		);
		return data.substr(17); /* Consumed 0-16 inclusive (17 bytes) */
		break;
	case 7:
		/* piece */
		// Need as many bytes as msglen says, minus the msgid byte
		// Decode index, byte offset, and block
		this._remotePieceMsg(
			decode4CharInt(data.substr(5,4))
			,decode4CharInt(data.substr(9,4))
			,data.substr(13,msglen - 9)
		);
		//return data.substr(17); /* Consumed 0-16 inclusive (17 bytes) */
		//log("peer.attemptMsg Bug check: msglen is " + msglen + " data is " + data.length + " msglen+4 is " + (msglen + 4));
		return data.substr(msglen + 4);
		break;
	case 8:
		/* cancel */
		// Need 12 more bytes
		if (data.length < (5 + 12)) {
			// Less than 17 bytes, not enough to read request msg
			throw this.EXCEPTION_DATA_SHORT;
		}
		// Decode index, byte offset, and length
		this._remoteCancelMsg(
			decode4CharInt(data.substr(5,4))
			,decode4CharInt(data.substr(9,4))
			,decode4CharInt(data.substr(13,4))
		);
		return data.substr(17); /* Consumed 0-16 inclusive (17 bytes) */
		break;

		
	case 9:
		/* port */
		// This one is odd.
		// Looking at http://wiki.theory.org/BitTorrentSpecification
		// I would expect the port param to be 4 bytes (like other numbers),
		// but it's 2 bytes. I expect that's because port range size is
		// 2^16. Meh.
		// Just in case, use msglen.
		// Need as many bytes as msglen says, minus the msgid byte
		// Decode index, byte offset, and block
		if (msglen == 3) {
			this._remotePortMsg(
				decode4CharInt("" + String.fromCharCode(0x00, 0x00) + data.substr(5,2))
			);
		} else if (msglen == 5) {
			this._remotePortMsg(
				decode4CharInt(data.substr(5,4))
			);
		}
		return data.substr(4+msglen); /* Consumed 0-(4+msglen - 1) inclusive (4+msglen bytes) */
		break;
	/*************************
	 * Cases 13-17 Are Fast Extension
	 * Official Extension BEP 6
	 * http://bittorrent.org/beps/bep_0006.html
	 *************************/
	case 13:
		/* Suggest Piece */

	case 14:
		/* Have All */
	case 15:
		/* Have None */
	case 16:
		/* Reject Request */			
	case 17:
		/* Allowed Fast */
	default:
		reportError("Peer._attemptMsg : Unknown msg id " + data.charCodeAt(4));
		throw this.EXCEPTION_INFO_VIOLATION;
	}

}

BasePeer.prototype._remoteHaveMsg = function(piece) {
    this._dispatchInformation({
            type:"HAVE"
            ,piece:piece
    });
}
BasePeer.prototype._remoteBitfieldMsgHelper = function(target) {
	var result="";
	var tmp = target;
	for (var i=0; i < 8; i++) {
		if ((tmp & 0x01) != 0x01) {
			result = "0" + result;
		} else {
			result = "1" + result;
		}
		tmp = tmp >> 0x01;
	}
	return result;
}
BasePeer.prototype._remoteBitfieldMsg = function(pieces) {
	// this._model.addEvent(new PeerBitfield(this));
	this._dispatchInformation({
            type:"BITFIELD"
            ,pieces:pieces
    });
}
BasePeer.prototype._remoteRequestMsg = function(index,begin,length) {
	this._dispatchInformation({
            type:"REQUEST"
            ,index:index
            ,begin:begin
            ,length:length
    });
	
}
BasePeer.prototype._remotePieceMsg = function(index,begin,block) {
    this._dispatchInformation({
            type:"PIECE"
            ,index:index
            ,begin:begin
            ,block:block
    });
}
BasePeer.prototype._remoteCancelMsg = function(index,begin,length) {
    this._dispatchInformation({
            type:"CANCEL"
            ,index:index
            ,begin:begin
            ,length:length
    });
}
BasePeer.prototype._remotePortMsg = function(port) {
	this._dispatchInformation({
            type:"PORT"
            ,port:port
    });	
}
const NS_ERROR_CONNECTION_REFUSED = 0x804B000D;
BasePeer.prototype._sendMsg = function(msg) {
	if (msg == null) {
		reportError("Peer._sendMsg Exception. trying to send null");
		throw new BadCodeException();
	}
	if (msg.length == 0) {
		reportError("Peer._sendMsg Exception. msg is zero length");
		throw new BadCodeException();
	}

	try {
		this.local.timeOfLastData = Date.now();
		if (this._outstream) {
			this._logComms("data","sent",msg);
			this._outstream.write(msg,msg.length);
		}
	} catch (e) {
/*
BT ERROR: Peer._sendMsg Exception. Exception: [Exception... "Component returned failure code: 0x804b000d (NS_ERROR_CONNECTION_REFUSED) [nsIOutputStream.write]"  nsresult: "0x804b000d (NS_ERROR_CONNECTION_REFUSED)"  location: "JS frame :: file:///home/matt/Projects/PhD/jsBittorrent/trunk/meadbittorrenthandler/components/bt.js :: anonymous :: line 2973"  data: no]
*/
		reportError("Peer._sendMsg Exception. Sent (hex): " + binaryToHex(msg) + " (binary):" + msg);
		reportError("Peer._sendMsg Exception. Exception: " + e);
		if (e.result == NS_ERROR_CONNECTION_REFUSED) {
			// !!!! Oh dear!
			log("Peer._sendMsg Exception : Connection Refused? Well then. That was rude.");
			log("Peer._sendMsg Exception : Remote is: " + this.remote.ip + ":" + this.remote.port);
			log("Peer._sendMsg Exception : We are: " + this._model.getValue("local_ip") + ":" + this._model.getValue("local_port"));
		}

	}
}

BasePeer.prototype._attemptHandshake = function(data) {
	// First byte is length of pstr
	// May need to have data as a stream, and use read byte.
	var pstrlen = data.charCodeAt(0);
	// Check data is correct length
	// Should be 1 byte (length of pstr), X bytes (pstr), 8 bytes (reserved, all 0s), 20 bytes (info hash), 20 bytes (peer id)
	// However, the last 20 bytes will not be sent if it's the tracker's NAT checking.
	if ((1 + pstrlen + 8 + 20 /* + 20 ignore peer id, in case it's the tracker */) > data.length) {
		// Problem. Wait.
		log("Peer _attemptHandshake : " + this.remote.ip + ":" + this.remote.port + " Data short! data is " + data.length + " pstrlen is " + pstrlen + " wanted " + (1 + pstrlen + 8 + 20));			
		throw this.EXCEPTION_DATA_SHORT;
		
	} else {
		// OK. Process handshake.
		// Ummm... how might we do this???
		
		var endpos = 0;
		var startpos = 0;
		var decoded = {};
		startpos = 1; endpos = 1+pstrlen; // index of last pstrlen char is pstrlen (because [0] is pstrlen itself)
						// So to use substring correctly, need to add 1 (index when to stop)
		decoded["pstr"] = data.substring(startpos,endpos);
		startpos = endpos; endpos = endpos+8;
		decoded["reserved"] = data.substring(startpos,endpos);
		startpos = endpos; endpos = endpos+20;
		decoded["info_hash"] = data.substring(startpos,endpos);
		
		this.remote.handshake_to_local_decoded = decoded;
		this._logComms("data","recvd",data.substr(0,endpos));
		return data.substr(endpos);
	}
}
BasePeer.prototype._attemptPeerId = function(data) {
	if (data.length < 20) {
		throw this.EXCEPTION_DATA_SHORT;
	}	
	this._logComms("data","recvd",data.substr(0,20));
	this.remote.handshake_to_local_decoded["peer_id"] = data.substr(0,20);
	// If we have a peer id, and it doesn't match the one we received, then we should disconnect
	if (this.remote.peer_id != false && this.remote.peer_id != this.remote.handshake_to_local_decoded["peer_id"]) {
		myDump("PeerEntity::_attemptPeerId peer id did not match: " + this.remote.peer_id + " and "
			+ this.remote.handshake_to_local_decoded["peer_id"]);
		reportError("Peer._attemptPeerId : Peer Id mismatch "
			 + binaryToHex(this.remote.peer_id) + " "
			 + binaryToHex(this.remote.handshake_to_local_decoded["peer_id"])
		);
		throw this.EXCEPTION_INFO_VIOLATION;
	} else if (this.remote.handshake_to_local_decoded["peer_id"] == this._model.getValue("peer_id")) {
		// connecting to ourself. D'oh.
		this._neverReconnect = true;
		log("Peer._attemptPeerId : Closing connection, connecting to ourself.");
		reportError("Peer._attemptPeerId : Peer Id matched self "
			 + binaryToHex(this.remote.peer_id) + " "
			 + binaryToHex(this.remote.handshake_to_local_decoded["peer_id"])
		);
		throw this.EXCEPTION_INFO_VIOLATION;
	}
	
	return data.substr(20);
	
}

var NSGetModule = XPCOMUtils.generateNSGetModule([BTPeerManager]);

